
# include "dfxWindow_Win32.h"
# include "dfxLog.h"

namespace DFX
{
    LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        Window_Win32 *w = NULL;

        if (msg == WM_CREATE)
        {
            w = (Window_Win32*)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLongPtr(hWnd, GWL_USERDATA, (LONG)w);
        }

        else
        {
            w = (Window_Win32*)GetWindowLongPtr(hWnd, GWL_USERDATA);

            if (!w)
                return DefWindowProc(hWnd, msg, wParam, lParam);
        }

        switch (msg)
        {
        case WM_CREATE:
            {
                WindowCallbackArrayIterator it = w->mCallbacks.begin();

                while (it != w->mCallbacks.end())
                {
                    (*it)->OnCreate(w);
                    it++;
                }
            }
            break;

        case WM_CLOSE:
        case WM_QUIT:
            {
                 WindowCallbackArrayIterator it = w->mCallbacks.begin();
                 
                 while (it != w->mCallbacks.end())
                 {
                     (*it)->OnDestroy(w);
                     it++;
                 }
                 
                 w->mClosed = true;
            }
            break;

        case WM_SIZE:
            {
                 WindowCallbackArrayIterator it = w->mCallbacks.begin();

                 while (it != w->mCallbacks.end())
                 {
                     (*it)->OnResize(w, LOWORD(lParam), HIWORD(lParam));
                     it++;
                 }
            }
            break;

        case WM_INPUT:
            {
                 UINT dwSize;
                 GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));

                 BYTE* lpb = new BYTE[dwSize];

                 GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER));
                 w->mRawInput = (RAWINPUT*)lpb;

                 if (w->mKeyboard && w->mRawInput->header.dwType == RIM_TYPEKEYBOARD) {
                     w->mKeyboard->Update(w);
                 }

                 SAFE_DELETE(lpb);
            }
        }

        return DefWindowProc(hWnd, msg, wParam, lParam);
    }

    Window_Win32::Window_Win32(const String &name, const String &className)
        : Window(name, className), mHWND(NULL), mHDC(NULL), mRC(NULL), mInstance(0)
    {
    }

    Window_Win32::~Window_Win32()
    {
        Destroy();
    }

    DFXRESULT Window_Win32::Create(UInt32 handle, UInt32 width, UInt32 height)
    {
        if (handle != (UInt32)-1)
        {
            mParameters = new WINDOW_PARAMETERS();
            mParameters->Hwnd = handle;
            mParameters->Width = width;
            mParameters->Height = height;

            mHWND = (HWND)handle;
            mHDC = GetDC(mHWND);

            return DFX_SUCCESS;
        }

        return DFX_CREATEFAILED;
    }

    DFXRESULT Window_Win32::Create(WINDOW_PARAMETERS *parameters)
    {
        if (!mInstance)
            mInstance = GetModuleHandle(NULL);

        mParameters = new WINDOW_PARAMETERS();
        memcpy(mParameters, parameters, sizeof(WINDOW_PARAMETERS));

        if (mParameters->Hwnd != (UInt32)-1)
        {
            mHWND = (HWND)mParameters->Hwnd;
            mHDC = GetDC(mHWND);

            return DFX_SUCCESS;
        }
        
        DEVMODE screenSettings;
        DWORD	dwStyle, dwExStyle;

        mRect.left = mParameters->Left;
        mRect.right = mParameters->Left + mParameters->Width;
        mRect.top = mParameters->Top;
        mRect.bottom = mParameters->Top + mParameters->Height;

        if (mParameters->FullScreen)
        {
            memset(&screenSettings, 0, sizeof(DEVMODE));

            screenSettings.dmSize = sizeof(DEVMODE);
            screenSettings.dmPelsWidth = mParameters->Width;
            screenSettings.dmPelsHeight = mParameters->Height;
            screenSettings.dmBitsPerPel = 32;
            screenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

            if (ChangeDisplaySettings(&screenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
            {
                DFX_WARNING("Unable to change display");
                mParameters->FullScreen = FALSE;
            }

            mParameters->Left = 0;
            mParameters->Top = 0;
        }
        else
        {
            if (mParameters->Left == 0 || mParameters->Top == 0)
            {
                RECT desktopRect;
                GetWindowRect(GetDesktopWindow(), &desktopRect);

                mParameters->Left = desktopRect.right / 2 - mParameters->Width / 2;
                mParameters->Top = desktopRect.bottom / 2 - mParameters->Height / 2;
            }
        }

        if (mParameters->FullScreen)
        {
            dwExStyle = WS_EX_APPWINDOW;
            dwStyle = WS_POPUP;
        }
        else
        {
            dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
            dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
        }

        AdjustWindowRectEx(&mRect, dwStyle, FALSE, dwExStyle);

        mHWND = CreateWindowEx(dwExStyle, mClassName.c_str(), "DFXEnigne - WINDOW", dwStyle, mParameters->Left, mParameters->Top,
            mRect.right - mRect.left, mRect.bottom - mRect.top, NULL, NULL, mInstance, this);

        if (!mHWND)
        {
            DFX_ERROR("Unable to create window : %s", mName.c_str());
            return DFX_CREATEWIN;
        }

        mHDC = GetDC(mHWND);

        return DFX_SUCCESS;
    }

    DFXRESULT Window_Win32::Destroy()
    {
        if (mParameters->FullScreen)
            ChangeDisplaySettings(NULL, 0);

        if (mHDC)
            ReleaseDC(mHWND, mHDC);

        if (mHWND)
            DestroyWindow(mHWND);

        SAFE_DELETE(mParameters);

        return DFX_SUCCESS;
    }

    void Window_Win32::SetTitle(const String &title)
    {
        SetWindowText(mHWND, title.c_str());
    }

    void Window_Win32::Show()
    {
        ShowWindow(mHWND, SW_SHOW);

        SetForegroundWindow(mHWND);
        SetFocus(mHWND);
    }

    void Window_Win32::SwapBuffers()
    {
        ::SwapBuffers(mHDC);
    }

    void Window_Win32::SetKeyboard(Keyboard *k)
    {
        mKeyboard = k;
    }
}